package ccpvspiral1;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;

import javax.swing.JTree;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import population.Population;
import population.PopulationInterface;

public class FileManager {

	public enum COMMAND {Input_Type, Input_Hdr, Input_Qty, Ascriptive_Size, Input_Values, BEGIN_DATA, COLUMNS_HDR, COLUMNS_ASCRIPTIVE, COLUMNS_DESCRIPTIVE, COLUMNS_VALUE, COLUMN_QTY, COLUMN_DATE, COLUMN_LOCATION, COLUMN_ASCRIPTIVE};
	public enum INPUT_TYPE {Tuple_Altruism, Ascriptive_Altruism, Group_Altruism, Groups, Ascriptive_Hierarchy};
	
	public static class Action {
		public COMMAND command;
		public String value;
		
		public Action(COMMAND command, String value) {
			this.command = command;
			this.value = value;
		}
	}

	/**
	 * Custom error for when file format deviates from the expected. 
	 * @author robert
	 *
	 */
	public static class FormatException extends Exception {

		private static final long serialVersionUID = 1L;
		public FormatException (String error){
			super(error);
		}
	}

	
	/**
	 * Parses the bufferedreader looking for commands. Ignores whitespace lines and comments (# at start of line).
	 * @param buff open file being read
	 * @return Action containing a command and an optional value
	 * @throws IOException
	 * @throws IllegalArgumentException
	 */
	public static Action findNextCommand(BufferedReader buff) throws IOException, IllegalArgumentException {
		String line = "";
		String value = "";
		String[] tokens = null;
		COMMAND cmd = null;
		boolean isFound = false;
		while ((!isFound)&&((line = buff.readLine()) != null)) { // short-circuit evaluation important here
			line = line.trim(); // trim whitespace from each end
			if (line.length() == 0) continue; // skip blank lines
			if (line.charAt(0) == '#') continue; // skip comments

			tokens = line.split(":");
			cmd = COMMAND.valueOf(tokens[0].trim());
			if (tokens.length > 1) {
                value = tokens[1].trim();
			} else {
				value = null;
			}
			isFound = true;
		}
		if (isFound) {
			return new FileManager.Action(cmd,value);	
		} else {
			return null;
		}
	}
	
	public static String findNextLine(BufferedReader buff) throws IOException, IllegalArgumentException  {
		String line = null;
		while ((line = buff.readLine()) != null) {
			if (line.length() == 0) continue; // skip blank lines
			if (line.charAt(0) == '#') continue; // skip comments
			break;
		}
		return line;
	}
	
	public static HashMap<COMMAND,String> findCommandList(BufferedReader buff) throws IOException, IllegalArgumentException {
		HashMap<COMMAND,String> actions = new HashMap<COMMAND,String>();
		String line = "";
		String value = "";
		String[] tokens = null;
		COMMAND cmd = null;
		boolean isFound = false;
		while (((line = buff.readLine()) != null)&&(!isFound)) {
			line = line.trim(); // trim whitespace from each end
			if (line.equals("BEGIN_DATA")) break; // end of command lines
			if (line.length() == 0) continue; // skip blank lines
			if (line.charAt(0) == '#') continue; // skip comments

			tokens = line.split(":");
			cmd = COMMAND.valueOf(tokens[0].trim());
			if (tokens.length > 1) {
                value = tokens[1].trim();
			} else {
				value = null;
			}
			actions.put(cmd,value);
		}
		return actions;
		
	}
	
	public static void csvReader(String filename) {
		BufferedReader buff;
		try {
			buff = new BufferedReader(new FileReader(filename));
		System.out.println("File opened.");
		
		RunTimeSettings rt = new RunTimeSettings();
		PopulationInterface popInt;
		// next 3 lines to get rid of warning about popInt not being initialized
		int[] zed = new int[1];
		zed[0] = 0;
		popInt = Population.getNewPopulationInterface(zed);
		
		Action act = null;
		while((act = findNextCommand(buff)) != null)
		{	
			System.out.println("Act value:" + act.value);
			if (act.command != COMMAND.Input_Type) {
				throw new FileManager.FormatException("File does not start with an Input_Type declaration.");
			}
			
			INPUT_TYPE	type = INPUT_TYPE.valueOf(act.value);
			System.out.println("Input type: '" + type + "' !!!!");
			switch(type) {
			case Tuple_Altruism:
				System.out.println("Importing Tuple_Altruism...");
				importTupleAltruism(buff,rt, false);
				break;
			case Group_Altruism:
				System.out.println("Importing Group_Altruism...");
				importTupleAltruism(buff,rt, true);
				break;
			case Groups:
				System.out.println("Importing Groups...");
				importGroups(buff,rt);
				popInt = Population.getNewPopulationInterface(rt.getAscriptiveLengths());// = new Population()
				System.out.println(rt.toString());
				break;
			case Ascriptive_Hierarchy:
				System.out.println("Importing Ascriptive Hierarchy...");
				importAscriptiveHierarchy(buff, popInt, rt);
				break;
			default: 
			
		  }
		}
		System.out.println("Done importing.");
		
		  buff.close();
		  
			rt.setPopulationInt(popInt);
			
		} catch (FileNotFoundException e) {
			// open file generates error
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// readline generates error
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (FormatException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		
	}
	
	
	
	private static void importAscriptiveHierarchy(BufferedReader buff, PopulationInterface pop, RunTimeSettings rt) {
		System.out.println("Importing Ascriptive Hierarchy");
		HashMap<COMMAND,String> actions = new HashMap<COMMAND,String>();
		try {
			actions = findCommandList(buff);
		} catch (IllegalArgumentException e1) {
			e1.printStackTrace();
		} catch (IOException e1) {
			e1.printStackTrace();
		}
		System.out.println("Metadata: ");
		for (COMMAND cmd : actions.keySet()) {
			System.out.println(cmd + " "+ actions.get(cmd));
		}
		int qty = Integer.parseInt(actions.get(COMMAND.Input_Qty));	
		String name = actions.get(COMMAND.COLUMN_ASCRIPTIVE);	
		
		int counter = 0;
		String line = "";
		String data = "";
		while ((counter < qty)&&(line != null)) {
			line = line.trim();
			data = data + "\n" + line;
			
			// End of Loop cleanup
			counter++;
			try {
				line = findNextLine(buff);
			} catch (IllegalArgumentException e) {
				// TODO Auto-generated catch block
				line = null;
			} catch (IOException e) {
				// TODO Auto-generated catch block
				line = null;
			}
			
		}
		pop.setHierarchicalStructureForAtttribute(rt.getAscriptiveIndex(name), data);
	}

	private static void importTupleAltruism(BufferedReader buff, RunTimeSettings rt, boolean isGroup) {
		HashMap<COMMAND,String> actions = new HashMap<COMMAND,String>();
		try {
			actions = findCommandList(buff);
		} catch (IllegalArgumentException e1) {
			e1.printStackTrace();
		} catch (IOException e1) {
			e1.printStackTrace();
		}
		System.out.println("Metadata: ");
		for (COMMAND cmd : actions.keySet()) {
			System.out.println(cmd + " "+ actions.get(cmd));
		}
		int qty = Integer.parseInt(actions.get(COMMAND.Input_Qty));	
		
		int counter = 0;
		String line = "";
		try {
			line = findNextLine(buff);
		} catch (IllegalArgumentException e1) {
			// TODO Auto-generated catch block
			line = null;
			e1.printStackTrace();
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			line = null;
			e1.printStackTrace();
		}
		while ((counter < qty)&&(line != null)) {
			line = line.trim();
			System.out.println(counter);
			String[] data = line.split(",");
			int num_cols = data.length;
			// so if we get 7 (0..6), then we have 3 ascriptive characteristics
			// 0,1,2 | 3,4,5 | 6
			ArrayList<Integer> chars1 = new ArrayList<Integer>();
			ArrayList<Integer> chars2 = new ArrayList<Integer>();
			
			int lpcount = 0;
			
			int year = Integer.parseInt(data[0]);
			double value = Double.parseDouble(data[num_cols-1]);
			System.out.println("test0 : " + data[0] + "|" + data[1] + "|" + data[2]);
			if (!isGroup) {
				System.out.println("Hi");
			  for (int i=1; i< 1+(num_cols-1)/2;i++) {
				  System.out.println("Hi2");
				  chars1.add(rt.getIndex(lpcount, data[i]));
			  	  lpcount++;
			  }
			  lpcount = 0;
			  for (int i=1+(num_cols-1)/2; i< 1+num_cols ;i++) {
				chars1.add(rt.getIndex(lpcount, data[i]));
			  	lpcount++;				
		  	  }
			  rt.addAltruism(chars1, chars2, year, value);
			} else {
				System.out.println("Hi3");
				  rt.addAltruism(data[1], data[2], year, value);
				  System.out.println("Hi4");
			}
			
			// Parse data

			
			
			// End of Loop cleanup
			counter++;
			try {
				if (counter < qty) line = findNextLine(buff);
			} catch (IllegalArgumentException e) {
				// TODO Auto-generated catch block
				line = null;
			} catch (IOException e) {
				// TODO Auto-generated catch block
				line = null;
			}
			
		}
		
	}

	/**
	 * Splits the string by the delimiter and trims whitespace from each substring. 
	 * @param splitter
	 * @param tosplit
	 * @return
	 */
	private static String[] trimSplit(String delimiter, String tosplit) {
		if (tosplit == null) {
			System.out.println("Input string tosplit is null!");
		}
		String[] trimmed = tosplit.split(delimiter);
		System.out.println("TrimSplit call 2: ");
		for (int i = 0; i<trimmed.length; i++) {
			System.out.println("trimsplit: " + i);
			trimmed[i] = trimmed[i].trim();
		}
		return trimmed;
	}
	
	/**
	 * Splits, trims, and converts the strings to integers.
	 * @param delimiter
	 * @param tosplit
	 * @return
	 */
	private static int[] trimSplitConvertInt(String delimiter, String tosplit) {
		System.out.println("TrimSplitInt call: '" + delimiter + "' " + tosplit );

		String[] trimmed = tosplit.split(delimiter);
		int[] result = new int[trimmed.length];
		for (int i = 0; i<trimmed.length; i++) {
			trimmed[i] = trimmed[i].trim();
			result[i] = Integer.parseInt(trimmed[i]);
		}
		return result;
	}
	
	/**
	 * Returns an arraylist of the values in 'values' corresponding to the indexes in 'indexes'.
	 * @param values
	 * @param indexes
	 * @return
	 */
	private static ArrayList<String> getIndexedValues(String[] values, int[] indexes) {
		ArrayList<String> results = new ArrayList<String>();
		for (int i=0; i<indexes.length; i++) {
			results.add(values[indexes[i]]);	
		}	
		return results;
	}
	
	/**
	 * Imports the population groups defined by their ascriptive characteristics and containing additional data fields. These groups are added to the runtime settings. 
	 * @param buff
	 * @param rt
	 */
	public static void importGroups(BufferedReader buff, RunTimeSettings rt) {
		HashMap<COMMAND,String> actions = new HashMap<COMMAND,String>();
		try {
			actions = findCommandList(buff);
		} catch (IllegalArgumentException e1) {
			e1.printStackTrace();
		} catch (IOException e1) {
			e1.printStackTrace();
		}
		System.out.println("Metadata: ");
		for (COMMAND cmd : actions.keySet()) {
			System.out.println(cmd + " "+ actions.get(cmd));
		}
		int qty = 0;
		boolean parseToEnd = false;
		if (actions.get(COMMAND.Input_Qty) == "TO_END") { 
			parseToEnd = true;
		}
		else {
			qty = Integer.parseInt(actions.get(COMMAND.Input_Qty));	
		}

		String[] header = trimSplit(",",actions.get(COMMAND.COLUMNS_HDR));
		int[] cols_ascriptive = trimSplitConvertInt(",",actions.get(COMMAND.COLUMNS_ASCRIPTIVE));
		int[] cols_descriptive = trimSplitConvertInt(",",actions.get(COMMAND.COLUMNS_DESCRIPTIVE));
		int[] cols_value = trimSplitConvertInt(",",actions.get(COMMAND.COLUMNS_VALUE));
		int col_qty = Integer.parseInt(actions.get(COMMAND.COLUMN_QTY));
		int col_date = Integer.parseInt(actions.get(COMMAND.COLUMN_DATE));
		int col_location = Integer.parseInt(actions.get(COMMAND.COLUMN_LOCATION));
		
		System.out.println("Meta-data extracted. Processing data.");
		
		rt.InitRunTimeSettings(getIndexedValues(header, cols_ascriptive));
		
		String line;
		int counter = 0;
		int year = 0;
		int location = 0;
		int index = 0;
		String group;
		ArrayList<Integer> values = new ArrayList<Integer>();
		ArrayList<Integer> character = new ArrayList<Integer>();
		DatedValues<Integer> dv;
		try {
			line = findNextLine(buff);
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			line = null;
			System.out.println("Illegal argument exception");
		} catch (IOException e) {
			// TODO Auto-generated catch block
			line = null;
			System.out.println("IO exception");
		}
		while ((counter < qty)&&(line != null)) {
			line = line.trim();
			values.clear();
			character.clear();
			
			String[] data = line.split(",");
			// Parse data
			year = Integer.parseInt(data[col_date]);
			if (counter == 0) {
				rt.setFirstYear(year);
			}
			if (cols_descriptive.length > 0) {
				group = data[cols_descriptive[0]];
			} else {
				group = "";
			}
			location = rt.getLocationIndex(data[col_location]);
			
			
			for (int i=0; i<cols_ascriptive.length; i++) {
				index = rt.getIndex(i, data[cols_ascriptive[i]]);// stores name, returns index
				character.add(index); // stores index of name
			}
			
			for (int i=0; i<cols_value.length; i++) {
				values.add(Integer.parseInt((data[cols_value[i]])));
			}
			
			
			dv = new DatedValues<Integer>(year,qty,values);
			System.out.println("Adding: "+group+" "+location+" "+character.toString()+" "+dv.toString());
			rt.addDatedValue(group, location, character, dv);
			
			// End of Loop cleanup
			counter++;
			System.out.println(counter);
			try {
				if (counter < qty) line = findNextLine(buff);
			} catch (IllegalArgumentException e) {
				// TODO Auto-generated catch block
				System.out.println("Illegal argument exception 2");
				line = null;
			} catch (IOException e) {
				// TODO Auto-generated catch block
				System.out.println("IO exception 2");
				line = null;
			}
			
		}
	
		System.out.println("End of groups import");
	}
	
	/**
	 * Example of how to read an xml file. Not used in current implementation.
	 * @param filename
	 */
	public void readXmlFile(String filename) {
		DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder docBuilder;
        Document doc;
		try {
			docBuilder = docBuilderFactory.newDocumentBuilder();
			doc = docBuilder.parse (new File(filename));
			doc.getDocumentElement().normalize();
			String root = doc.getDocumentElement().getNodeName();
			System.out.println("Processing file: "+filename+" with root: "+root+"\n");
			
			NodeList listOfPersons = doc.getElementsByTagName("person");
	/*		loadSociety(doc);
			loadGeography(doc);
			loadDemographics(doc);
			loadInterventions(doc);
			loadIdeologies(doc);
			loadHistory(doc);
			loadRuntime(doc);
			*/
		}
		catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		 catch (SAXException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
		} 
		 catch (ParserConfigurationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	
	
	/**
	 * Imports the agent and runtime specifications from a formatted CSV file.
	 * @param filename
	 * @return
	 */
	public static RunTimeSettings importAgentLayer(String filename) {
		
		RunTimeSettings rt = new RunTimeSettings();
		
		/*
		 * Get list of column names
		 * Extract categories of ascriptive characteristics
		 * For each row
		 *   convert first element to Integer (year)
		 *   second element is the name in a new epoch
		 *   second to last element is count in epoch
		 *   last element is power in epoch
		 * 
		 */
		
		
		int year = 0;
		String group = "";
		int qty = 0;
		int location = 0;
		int total = 0;
		ArrayList<String> temp = new ArrayList<String>();
		ArrayList<Integer> values = new ArrayList<Integer>();
		ArrayList<Integer> character = new ArrayList<Integer>();
		DatedValues<Integer> dv;
		BufferedReader buff;
		try {
			buff = new BufferedReader(new FileReader(filename));
		System.out.println("File opened.");
		String line = null;
		int row = 0, col=0;
		String token = "";
		while((line = buff.readLine()) != null)
		{

			System.out.println("Reading row: " + row);
			StringTokenizer tokens = new StringTokenizer(line,",");
			total = tokens.countTokens();
			
			if (row == 0) {
				System.out.print(" Columns: ");
				col = 0;
			  while (tokens.hasMoreTokens())
			  {
				  System.out.print("\n  "+col+": ");
				token = tokens.nextToken();
				System.out.print(token);
					if (!((col == 0)||(col==1)||(col==2)||(col==(total-1))||(col==(total-2)))) {
						//System.out.println("Inner loop!");
						System.out.println("\nAdding: "+token);
					  temp.add(token);
					}
					col++;
				}
			  System.out.println("\nValues contents: "+temp.toString());
			    rt = new RunTimeSettings(temp);
			    temp.clear();
			    System.out.println();
			} else {
				System.out.print(" col: ");
				col = 0;
				values.clear();
				character.clear();
				year = 0;
				group = "";
				location = 9999;
				while (tokens.hasMoreTokens()) {
					token = tokens.nextToken();
					  System.out.print("\n "+col+": ");
					  System.out.print(token);
					  
					switch(col) {
					case 0:
						year = Integer.parseInt(token);
						break;
					case 1: 
						group = token;
						break;
					case 2:
						location = rt.getLocationIndex(token);
						break;
					default: // case doesn't allow expressions
						if (col == (total-1)) {
							values.add(Integer.parseInt(token)); // power
						} else if (col == (total-2)) {
						    qty = Integer.parseInt(token);
							break;
						} else { // default
							
							int index = rt.getIndex(col-3, token);
							System.out.print(" ("+index+")");
							character.add(index); // adds name, stores index
						}
						break;
					}
					
					col++;
				}
				if (row == 1) {
					rt.setFirstYear(year);
				}
				dv = new DatedValues<Integer>(year,qty,values);
				System.out.println("\nAdding: "+group+" "+location+" "+character.toString()+" "+dv.toString());
				rt.addDatedValue(group, location, character, dv);
				System.out.print("\nCharacter: ");
				for(int i: character) {
					System.out.print(" "+i);
				}
				System.out.println();
			}
			
			row++;
		  }
		  buff.close();
		} catch (FileNotFoundException e) {
			// open file generates error
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// readline generates error
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return rt;
	}
	
	public void csvWriter(String filename, RunTimeSettings rt) {
		// create file
		// write header comments
		// output groups
		// output altruism
		// output ascriptive hierarchy
		// close file
	}
	
	
}
